using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Net;

namespace CueCatDecoder
{
    public class Decoder
    {
        static Decoder()
        {
            decodingTable = new Dictionary<string, int>[3];
            decodingTable[0] = new Dictionary<string, int>();
            decodingTable[1] = new Dictionary<string, int>();
            decodingTable[2] = new Dictionary<string, int>();

            decodingTable[0]["Z"] = 0;
            decodingTable[0]["Y"] = 1;
            decodingTable[0]["X"] = 2;
            decodingTable[0]["W"] = 3;
            decodingTable[0]["3"] = 4;
            decodingTable[0]["2"] = 5;
            decodingTable[0]["1"] = 6;
            decodingTable[0]["0"] = 7;
            decodingTable[0]["7"] = 8;
            decodingTable[0]["6"] = 9;

            decodingTable[1]["C3"] = 0;
            decodingTable[1]["CN"] = 1;
            decodingTable[1]["Cx"] = 2;
            decodingTable[1]["Ch"] = 3;
            decodingTable[1]["D3"] = 4;
            decodingTable[1]["DN"] = 5;
            decodingTable[1]["Dx"] = 6;
            decodingTable[1]["Dh"] = 7;
            decodingTable[1]["E3"] = 8;
            decodingTable[1]["EN"] = 9;

            decodingTable[2]["n"] = 0;
            decodingTable[2]["j"] = 1;
            decodingTable[2]["f"] = 2;
            decodingTable[2]["b"] = 3;
            decodingTable[2]["D"] = 4;
            decodingTable[2]["z"] = 5;
            decodingTable[2]["v"] = 6;
            decodingTable[2]["r"] = 7;
            decodingTable[2]["T"] = 8;
            decodingTable[2]["P"] = 9;
        

        }

        private static Dictionary<string, int>[] decodingTable;

        /// <summary>
        /// Algorithm derived from Daniel Hulls's JavaScript CueCat Decoder derived 
        /// from the original at www.mp3car.com. Translated to C# by Scott Hanselman.
        /// </summary>
        /// <param name="code">The Raw CueCat String</param>
        /// <returns>The decoded cuecat number as a string</returns>
        public static Product Decode(string code)
        {
            if(string.IsNullOrEmpty(code) || code.Length == 0)
            {
                throw new ArgumentOutOfRangeException("Value CueCat input must be quite a bit longer...");
            }

            string retVal = String.Empty;
            string[] sections = code.Split('.');

            // check to see if valid ISBN first for new CueCat?
            if (code.StartsWith("978"))
            {
                string isbn = ConvertUPCtoISBN(code);
                return new Product(isbn, IdType.ISBN, code);
            }
            else
            {
            // Just get the part with the 
                if (sections.Length != 5)
                {
                    throw new ArgumentOutOfRangeException("Value CueCat input has 4 periods...");
                }
                code = sections[3];

                int charcount = 0;
                code = code.Replace(".", "");
                    
                for (int i = 0; i < code.Length; i++)
                {
                    //check if it's in the one of the single char tables?
                    int index = (charcount + 1) % 3;
                    string valueToCheck = code[i].ToString();
                    if (decodingTable[index].ContainsKey(valueToCheck))
                    {
                        retVal += decodingTable[index][valueToCheck];
                        charcount++;
                    }
                    else //check a double
                    {
                        if (i + 1 == code.Length) break;
                        string valueToCheck2 = valueToCheck + code[i + 1].ToString();
                        if (decodingTable[index].ContainsKey(valueToCheck2))
                        {
                            retVal += decodingTable[index][valueToCheck2];
                            charcount++;
                            //we did a double char, jump over...
                            i++;
                        }
                    }
                }

                if (retVal.StartsWith("978"))
                {
                    string isbn = ConvertUPCtoISBN(retVal);
                    return new Product(isbn, IdType.ISBN, retVal);
                }

                return new Product(retVal, IdType.UPC);
            }
        }

        public static string ConvertUPCtoISBN(string code)
        {
            if (code.StartsWith("978") == false)
            {
                throw new ArgumentOutOfRangeException("UPCs that might be books have length 13 and start with 978");
            }
            code = code.Substring(3,9);
            int xsum = 0;
            for (int i = 0; i < 9; i++)
            {
                xsum += (10 - i) * int.Parse(code[i].ToString());
            }

            xsum %= 11;
            xsum = 11 - xsum;

            String val = xsum.ToString();

            switch (xsum)
            {
                case 10: val = "X"; break;
                case 11: val = "0"; break;
            }

            code += val;
            return code; //Now an ISBN
        }
    }

    public class Product
    {
        public string ID = "None";
        public string BarCode = "None";
        public IdType Type;

        public Product(string id, IdType type, string barCode)
        {
            ID = id;
            Type = type;
            BarCode = barCode;
        }

        public Product(string id, IdType type) : this(id, type, id) { }
    }
    public enum IdType
    {
        Unknown,
        ISBN,
        UPC
    }
}
